Este relatório analisa o treino matinal de remo no Yacht Club da Bahia em 31 de Janeiro de 2026. Os dados de GPS são baixados da plataforma Treinus e os dados ambientais da boia SIMCOSTA 515. O relatório calcula os trechos mais rápidos de 500 m em linha reta e examina como vento e corrente influenciaram cada desempenho.
library(yachtvaa)
library(dplyr)
library(sf)
library(ggplot2)
library(lubridate)
Baixa os registros GPS dos remadores e os dados da boia SIMCOSTA 515
em uma única chamada. Com cache = TRUE os dados são lidos
do disco sem necessidade de autenticação.
if (!params$cache) {
session <- treinusr::treinus_auth()
}
use_cache <- params$cache
raw <- fetch_session_data(
session = session,
date = params$date,
start_time = params$start_time,
end_time = params$end_time,
athlete_ids = params$athlete_ids,
cache = use_cache,
overwrite_db=!use_cache,
use_memoise=FALSE
)
records <- raw$records
buoy <- raw$buoy
# Filtro espacial: manter apenas pontos GPS dentro da área de estudo
records_sf <- records_to_sf(records)
study_area <- sf::st_bbox(
c(xmin = -38.61380, ymin = -13.00741,
xmax = -38.46308, ymax = -12.81308),
crs = 4326L
) |>
sf::st_as_sfc() |>
sf::st_transform(sf::st_crs(records_sf))
records_sf <- records_sf[lengths(sf::st_intersects(records_sf, study_area)) > 0, ]
Nem todo atleta retornado pelo Treinus realmente remou – alguns podem ter registros GPS breves em terra. Mantemos apenas atletas com pelo menos 100 pontos GPS e percurso acumulado superior a 200 m.
track_summary <- records_sf |>
st_drop_geometry() |>
summarise(
n_fixes = n(),
first_fix = min(timestamp),
last_fix = max(timestamp),
duration_min = as.numeric(difftime(max(timestamp), min(timestamp),
units = "mins")),
.by = c(id_athlete, fullname_athlete)
)
# Distância acumulada ponto a ponto por atleta
track_distance <- records_sf |>
arrange(id_athlete, timestamp) |>
mutate(
coords = st_coordinates(geometry),
x = coords[, 1],
y = coords[, 2],
.keep = "unused"
) |>
st_drop_geometry() |>
summarise(
track_distance_m = sum(sqrt(diff(x)^2 + diff(y)^2)),
.by = id_athlete
)
track_summary <- track_summary |>
left_join(track_distance, by = "id_athlete")
paddlers <- track_summary |>
filter(n_fixes >= 100, track_distance_m >= 200)
paddlers |>
arrange(desc(track_distance_m)) |>
mutate(
track_distance_km = round(track_distance_m / 1000, 1),
duration_min = round(duration_min, 0)
) |>
select(fullname_athlete, n_fixes, duration_min, track_distance_km) |>
knitr::kable(
col.names = c("Atleta", "Pontos GPS", "Duração (min)", "Distância (km)"),
caption = "Atletas identificados na água"
)
| Atleta | Pontos GPS | Duração (min) | Distância (km) |
|---|---|---|---|
| Victor Patiri | 1912 | 114 | 13.9 |
| Eduardo Valdes Sanchez | 887 | 102 | 10.3 |
| Fernanda Siqueira | 807 | 74 | 9.9 |
| RICARDO RAPPEL | 1027 | 80 | 9.4 |
| Verena Barbara Carneiro | 1037 | 74 | 9.3 |
| Carita Souza | 725 | 64 | 9.2 |
| Rosário Calmon | 571 | 70 | 9.2 |
| JULIANA RAPPEL | 727 | 84 | 9.2 |
| Maria Regina Valente | 1020 | 68 | 9.0 |
| Izabela Luz | 632 | 68 | 8.9 |
| Stela de Sá Hama | 820 | 68 | 8.9 |
| José Barretto | 3322 | 55 | 8.1 |
| ANA LUCIA BERENGUER | 718 | 54 | 7.9 |
| Eduardo Leoni | 2591 | 125 | 5.0 |
records_sf <- records_sf |>
filter(id_athlete %in% paddlers$id_athlete)
n_paddlers <- nrow(paddlers)
arrow_df <- fast500 |>
as_tibble()
basemap <- maptiles::get_tiles(records_sf, provider = "CartoDB.Positron",
crop = TRUE, verbose = FALSE)
ggplot() +
tidyterra::geom_spatraster_rgb(data = basemap) +
geom_sf(data = records_sf, colour = "grey50", size = 0.1, alpha = 0.4) +
geom_segment(
data = arrow_df,
aes(x = start_x, y = start_y, xend = end_x, yend = end_y,
colour = avg_speed_kmh),
arrow = arrow(length = unit(0.3, "cm"), type = "closed"),
linewidth = 1.3
) +
ggrepel::geom_text_repel(
data = arrow_df,
aes(x = (start_x + end_x) / 2, y = (start_y + end_y) / 2,
label = fullname_athlete),
size = 2.5, max.overlaps = 20,
bg.color = "white", bg.r = 0.15
) +
scale_colour_viridis_c(option = "plasma", name = "Vel. (km/h)") +
coord_sf(crs = sf::st_crs(records_sf)) +
labs(
title = paste("Trechos mais rápidos de 500 m --", date_short),
subtitle = "Setas indicam a direção do percurso; cor = velocidade do atleta"
) +
theme_void(base_size = 11) +
theme(
legend.position = "bottom",
plot.title = element_text(face = "bold"),
plot.margin = margin(5, 5, 5, 5)
)
Trechos mais r<U+00E1>pidos de 500 m em linha reta. Setas indicam a dire<U+00E7><U+00E3>o do percurso.
Interpolar os dados da boia em uma grade regular de 10 minutos.
buoy_ip <- interpolate_buoy(buoy)
has_wind <- "wind_speed" %in% names(buoy_ip) &&
any(!is.na(buoy_ip$wind_speed))
buoy_wind <- buoy_ip |>
filter(!is.na(wind_speed)) |>
mutate(
wind_speed_kmh = wind_speed * 3.6,
local_time = with_tz(datetime, tzone = "America/Bahia")
)
p_speed <- ggplot(buoy_wind, aes(local_time, wind_speed_kmh)) +
geom_line(linewidth = 0.8, colour = "#0072B2") +
geom_point(size = 1.5, colour = "#0072B2") +
labs(y = "Velocidade do vento (km/h)", x = NULL, title = "Condições de vento") +
theme_minimal(base_size = 11)
p_dir <- ggplot(buoy_wind, aes(local_time, wind_direction)) +
geom_line(linewidth = 0.8, colour = "#D55E00") +
geom_point(size = 1.5, colour = "#D55E00") +
scale_y_continuous(
limits = c(0, 360),
breaks = seq(0, 360, 90),
labels = c("N", "E", "S", "W", "N")
) +
labs(y = "Direção do vento (de)", x = "Hora local (America/Bahia)") +
theme_minimal(base_size = 11)
gridExtra::grid.arrange(p_speed, p_dir, ncol = 1)
Velocidade e dire<U+00E7><U+00E3>o do vento registradas pela boia SIMCOSTA 515.
cat("**Sem dados de vento disponíveis para este treino.** O vento será tratado como zero.\n")
buoy_current <- buoy_ip |>
filter(!is.na(current_speed_kmh)) |>
mutate(local_time = with_tz(datetime, tzone = "America/Bahia"))
p_cspeed <- ggplot(buoy_current, aes(local_time, current_speed_kmh)) +
geom_line(linewidth = 0.8, colour = "#009E73") +
geom_point(size = 1.5, colour = "#009E73") +
labs(y = "Velocidade da corrente (km/h)", x = NULL, title = "Condições de corrente") +
theme_minimal(base_size = 11)
p_cdir <- ggplot(buoy_current, aes(local_time, current_direction)) +
geom_line(linewidth = 0.8, colour = "#CC79A7") +
geom_point(size = 1.5, colour = "#CC79A7") +
scale_y_continuous(
limits = c(0, 360),
breaks = seq(0, 360, 90),
labels = c("N", "E", "S", "W", "N")
) +
labs(y = "Direção da corrente (para)", x = "Hora local (America/Bahia)") +
theme_minimal(base_size = 11)
gridExtra::grid.arrange(p_cspeed, p_cdir, ncol = 1)
Velocidade e dire<U+00E7><U+00E3>o da corrente registradas pela boia 515.
Associar cada segmento à observação mais próxima da boia via rolling join e calcular ângulos relativos de vento/corrente.
buoy_for_match <- buoy_ip |>
transmute(
datetime,
wind_direction_deg = if ("wind_direction" %in% names(buoy_ip))
wind_direction else NA_real_,
wind_speed_kmh = if ("wind_speed" %in% names(buoy_ip))
wind_speed * 3.6 else NA_real_,
current_direction_deg = current_direction,
current_speed_kmh = current_speed_kmh,
wave_height,
wave_direction
)
matched <- match_buoy_to_segments(fast500, buoy_for_match)
Calcular ângulos relativos de vento/corrente e componentes ao longo do percurso. Quando dados de vento não estão disponíveis, são imputados como zero.
conditions <- apparent_conditions(matched, impute_missing_wind = !has_wind)
Direção da corrente em relação ao rumo do trecho mais rápido de cada atleta. Topo = corrente a favor (de popa), base = corrente contrária (de proa). O raio indica a intensidade da corrente.
current_polar <- conditions |>
as_tibble() |>
filter(!is.na(current_speed_kmh)) |>
mutate(
current_relative_deg = (current_direction_deg - bearing_deg) %% 360
)
current_r_max <- max(5, max(current_polar$current_speed_kmh, na.rm = TRUE))
ggplot(current_polar, aes(x = current_relative_deg, y = current_speed_kmh)) +
geom_point(aes(colour = avg_speed_kmh), size = 3) +
ggrepel::geom_text_repel(
aes(label = fullname_athlete),
size = 2.5, max.overlaps = 20
) +
coord_polar(start = 0, direction = -1) +
scale_x_continuous(
limits = c(0, 360),
breaks = c(0, 90, 180, 270),
labels = c("A favor", "90\u00b0", "Contra", "270\u00b0")
) +
scale_y_continuous(limits = c(0, current_r_max)) +
scale_colour_viridis_c(option = "plasma", name = "Vel. atleta\n(km/h)") +
labs(
title = "Corrente relativa ao rumo do trecho mais rápido",
subtitle = "Topo = a favor, base = contrária; cor = velocidade do atleta",
x = NULL,
y = "Vel. corrente (km/h)"
) +
theme_minimal(base_size = 11)
Corrente relativa ao rumo: topo = a favor, base = contr<U+00E1>ria. Raio = velocidade da corrente.
Mesma lógica aplicada ao vento. Topo = vento a favor (de popa), base = vento contrário (de proa).
wind_polar <- conditions |>
as_tibble() |>
filter(!is.na(wind_speed_kmh)) |>
mutate(
wind_relative_deg = (wind_direction_deg + 180 - bearing_deg) %% 360
)
wind_r_max <- max(30, max(wind_polar$wind_speed_kmh, na.rm = TRUE))
ggplot(wind_polar, aes(x = wind_relative_deg, y = wind_speed_kmh)) +
geom_point(aes(colour = avg_speed_kmh), size = 3) +
ggrepel::geom_text_repel(
aes(label = fullname_athlete),
size = 2.5, max.overlaps = 20
) +
coord_polar(start = 0, direction = -1) +
scale_x_continuous(
limits = c(0, 360),
breaks = c(0, 90, 180, 270),
labels = c("A favor", "90\u00b0", "Contra", "270\u00b0")
) +
scale_y_continuous(limits = c(0, wind_r_max)) +
scale_colour_viridis_c(option = "plasma", name = "Vel. atleta\n(km/h)") +
labs(
title = "Vento relativo ao rumo do trecho mais rápido",
subtitle = "Topo = a favor, base = contrário; cor = velocidade do atleta",
x = NULL,
y = "Vel. vento (km/h)"
) +
theme_minimal(base_size = 11)
Vento relativo ao rumo: topo = a favor, base = contr<U+00E1>rio. Raio = velocidade do vento.
cat("**Sem dados de vento disponíveis.** Gráfico polar de vento omitido.\n")
league <- build_league_table(conditions, athlete_col = "fullname_athlete")
league_fmt <- format_league(league, top_n = 15)
league_fmt |>
select(
rank, fullname_athlete, predicted_time_fmt, avg_speed_kmh,
wind_class, wind_component_kmh,
current_class, current_component_kmh
) |>
mutate(
avg_speed_kmh = round(avg_speed_kmh, 1),
wind_component_kmh = round(wind_component_kmh, 1),
current_component_kmh = round(current_component_kmh, 1)
) |>
knitr::kable(
col.names = c(
"Pos.", "Atleta", "Tempo", "Vel. (km/h)",
"Vento", "Vento (km/h)", "Corrente", "Corrente (km/h)"
),
caption = paste("Classificação: 500 m mais rápidos em", date_short)
)
| Pos. | Atleta | Tempo | Vel. (km/h) | Vento | Vento (km/h) | Corrente | Corrente (km/h) |
|---|---|---|---|---|---|---|---|
| 1 | Fernanda Siqueira | 2:43.2 | 11.0 | tailwind | 13.6 | cross_left | 1.0 |
| 2 | José Barretto | 2:52.8 | 10.4 | headwind | -12.7 | opposing | -1.7 |
| 3 | Carita Souza | 2:57.1 | 10.2 | tailwind | 13.7 | following | 1.1 |
| 4 | Victor Patiri | 3:01.8 | 9.9 | headwind | -12.6 | opposing | -1.5 |
| 5 | ANA LUCIA BERENGUER | 3:03.6 | 9.8 | headwind | -13.4 | opposing | -1.6 |
| 6 | Verena Barbara Carneiro | 3:06.8 | 9.6 | headwind | -13.6 | opposing | -1.5 |
| 7 | Rosário Calmon | 3:07.7 | 9.6 | headwind | -12.4 | opposing | -1.5 |
| 8 | Maria Regina Valente | 3:12.9 | 9.3 | headwind | -13.5 | opposing | -1.5 |
| 9 | Izabela Luz | 3:13.3 | 9.3 | headwind | -12.4 | opposing | -1.5 |
| 10 | Stela de Sá Hama | 3:16.2 | 9.2 | headwind | -13.6 | opposing | -1.5 |
| 11 | RICARDO RAPPEL | 3:21.9 | 8.9 | headwind | -13.9 | opposing | -1.4 |
| 12 | Eduardo Leoni | 3:37.7 | 8.3 | headwind | -11.7 | following | 0.3 |
| 13 | JULIANA RAPPEL | 3:41.2 | 8.1 | headwind | -12.8 | opposing | -1.5 |
| 14 | Eduardo Valdes Sanchez | 3:49.4 | 7.8 | headwind | -13.4 | cross_right | -0.2 |
current_summary <- buoy_current |>
summarise(
mean_speed = round(mean(current_speed_kmh, na.rm = TRUE), 2),
max_speed = round(max(current_speed_kmh, na.rm = TRUE), 2),
mean_dir = round(mean(current_direction, na.rm = TRUE), 0)
)
winner <- league_fmt$fullname_athlete[1]
winner_time <- league_fmt$predicted_time_fmt[1]
winner_kmh <- round(league_fmt$avg_speed_kmh[1], 1)
if (has_wind) {
wind_summary <- buoy_wind |>
summarise(
mean_speed = round(mean(wind_speed_kmh, na.rm = TRUE), 1),
max_speed = round(max(wind_speed_kmh, na.rm = TRUE), 1),
mean_dir = round(mean(wind_direction, na.rm = TRUE), 0)
)
wind_line <- sprintf(
"- **Vento:** média %s km/h (máx %s), predominante de %s°",
wind_summary$mean_speed, wind_summary$max_speed, wind_summary$mean_dir
)
} else {
wind_line <- "- **Vento:** sem dados disponíveis (imputado como zero)"
}
cat(sprintf(
"**Resumo do treino:**\n\n
- **Data:** %s
- **Atletas na água:** %d
%s
- **Corrente:** média %s km/h (máx %s), fluindo para %s°
- **500 m mais rápido:** %s em %s (%s km/h)\n",
date_label,
n_paddlers,
wind_line,
current_summary$mean_speed, current_summary$max_speed, current_summary$mean_dir,
winner, winner_time, winner_kmh
))
Resumo do treino: